use serde::{Deserialize, Serialize};
use ntex::web::{ErrorRenderer, FromRequest};
use std::future::Future;
use crate::{CustomError, AppState};
use std::sync::Arc;
use ntex::http::HttpMessage;
use cookie::Cookie;

//前端 Github 授权登录后的返回的 code
#[derive(Debug, Clone, Deserialize)]
pub struct Login {
    pub code: String,
}


//前端 Github 授权登录后，使用 code 换取的 access_token
#[derive(Debug, Clone, Deserialize)]
pub struct AccessToken {
    pub access_token: String,
}

//gitHub 返回的用户信息
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GetHubUserInfo {
    pub id: u32,            //用户id
    pub name: String,       //用户名(不是昵称)
    pub avatar_url: String, //头像链接
}


//网站的所有用户 （包括管理员）用于身份验证
#[derive(Debug, Clone)]
pub struct User {
    pub id: u32,
}

//网站的管理员 用于身份验证
#[derive(Debug, Clone)]
pub struct Admin {
    pub id: u32,
}



impl<E: ErrorRenderer> FromRequest<E> for User {
    type Error = CustomError;
    // type Future = Pin<Box<dyn Future<Output = Result<Self, Slef::Error>>>>;

    fn from_request(
            req: &ntex::web::HttpRequest,
            _: &mut ntex::http::Payload,
        ) -> impl Future<Output = Result<Self, Self::Error>> {
        
        let db_pool = Arc::clone(
            req.app_state::<Arc<AppState>>().unwrap()
        ).db_pool.clone();

        let access_token = req.cookie("ACCESS_TOKEN");
    
        let fut = async move {
            let access_token = match access_token {
                Some(c) => c,
                None => return Err(CustomError::AuthFailed("你还没有登录，请先登录".into())),
            };

            let user_id = match get_user_id(&access_token).await {
                Ok(id) => id,
                Err(e) => return Err(e),
            };

            if sqlx::query("SELECT * FROM users WHERE id = ?")
                .bind(user_id)
                .fetch_optional(&db_pool)
                .await?
                .is_none() {
                    return Err(
                        CustomError::AuthFailed("你没有在本站使用 GitHub 登录过，请先登录".into())
                    );
                }
            
            Ok(Self { id: user_id })
        };
        
        Box::pin(fut)
    }
}


impl<E: ErrorRenderer> FromRequest<E> for Admin {
    type Error = CustomError;
    // type Future = Pin<Box<dyn Future<Output = Result<Self, Slef::Error>>>>;

    fn from_request(
            req: &ntex::web::HttpRequest,
            _: &mut ntex::http::Payload,
        ) -> impl Future<Output = Result<Self, Self::Error>> {
        
        let db_pool = Arc::clone(
            req.app_state::<Arc<AppState>>().unwrap()
        ).db_pool.clone();

        let access_token = req.cookie("ACCESS_TOKEN");
    
        let fut = async move {
            let access_token = match access_token {
                Some(c) => c,
                None => return Err(CustomError::AuthFailed("你还没有登录，请先登录".into())),
            };

            let user_id = match get_user_id(&access_token).await {
                Ok(id) => id,
                Err(e) => return Err(e),
            };

            if sqlx::query("SELECT * FROM users WHERE id = ?")
                .bind(user_id)
                .fetch_optional(&db_pool)
                .await?
                .is_some() {
                    //查到了，需要判断用户id 是不是管理员id
                    if user_id != 1 { 
                        return Err(
                            CustomError::AuthFailed("你没有权限访问此页面".into())
                        );
                    }
                } else {
                    return Err(
                        CustomError::AuthFailed("你没有在本站使用 GitHub 登录过，请先登录".into())
                    );
                }
            
            Ok(Self { id: user_id })
        };
        
        Box::pin(fut)
    }
}



async fn get_user_id<'a>(access_token: &'a Cookie<'a>) -> Result<u32, CustomError> {
    let client = reqwest::Client::new();

    let user_info = client
        .get("https://api.github.com/user")
        .bearer_auth(access_token.value())
        .header("User-Agent", "lightning")
        .send()
        .await;

    let user_id = match user_info { 
        Ok(r) => {
            match r.json::<GetHubUserInfo>().await {
                Ok(i) => i.id,
                Err(_) => return Err(
                    CustomError::BadRequest("无法获取 Github 用户信息， 可能是提供了
                        不正确的 access token, 请重新登录".into())),
            }
        }
        Err(_) => return Err(
            CustomError::InternalServerError("无法获取 Github 用户信息，请稍后再试".into())
        )
    };

    Ok(user_id)
}